#include "NetworkHandler.h"

#include <iostream>

void *runInput(void *networkHandler){
	NetworkInputInfo *handlerInfo = (NetworkInputInfo*)networkHandler;
	NetworkHandler *handler = handlerInfo->handler;
	handler->checkIncoming(handlerInfo->streamToWatch);
	free(handlerInfo);
}

void *runOutput(void *networkHandler){
	NetworkHandler *handler = (NetworkHandler*)networkHandler;
	handler->checkOutgoing();
}

NetworkHandler::NetworkHandler(){
	//set up the buffer and its protection
	bufferPos = 0;
	pthread_mutex_init(&bufferLock, NULL);
	pthread_cond_init(&isBufferEmpty, NULL);
	sendAll = 0;
	pthread_mutex_init(&sendAllLock, NULL);
	//set up connection data structure protection
	numConnections = 0;
	pthread_mutex_init(&connectionLock, NULL);
	pthread_mutex_init(&targetLock, NULL);
	//make a note of socket state
	socketState = SDLNet_AllocSocketSet(MAXCOMS);
	if(!socketState){exit(1);}
	//start the threads
	live = 1;
	pthread_create( &outputThread, NULL, runOutput, this);
}

NetworkHandler::~NetworkHandler(){
	//kill the threads
	live = 0;
	pthread_join( outputThread, NULL );
	//and then leave
	SDLNet_FreeSocketSet(socketState);
}

void NetworkHandler::addBroadcastMessage(char *message, int length, int recipient){
	pthread_mutex_lock( &bufferLock );
	
	while( ((length + 4 + 4) + bufferPos) > BUFFERSIZE ){
		if(bufferPos == 0){exit(1);}
		broadcastMessages();
		pthread_cond_wait(&isBufferEmpty, &bufferLock);
	}
	//first write the recipient
	SDLNet_Write32(recipient, (buffer + bufferPos));
	bufferPos += 4;
	//then the length
	SDLNet_Write32(length, (buffer + bufferPos));
	bufferPos += 4;
	//then the message
	memcpy( (buffer + bufferPos), message, length );
	bufferPos += length;
	
	pthread_mutex_unlock( &bufferLock );
}

void NetworkHandler::broadcastMessages(){
	pthread_mutex_lock( &sendAllLock );
	sendAll = 1;
	pthread_mutex_unlock( &sendAllLock );
}

void NetworkHandler::checkOutgoing(){
	while(live){
		//std::cout << "O\n";
		checkOutputToNetwork();
		pthread_yield();
	}
}

void NetworkHandler::checkOutputToNetwork(){
	pthread_mutex_lock( &bufferLock );
	pthread_mutex_lock( &sendAllLock );
	pthread_mutex_lock( &connectionLock );
	if(sendAll || ( bufferPos > (BUFFERSIZE/2) )){
		sendAll = 0;
		//send data
		if(bufferPos > 0){
			//std::cout << "Sending " << bufferPos << " bytes.\n";
			int i;
			for(i = 0; i<numConnections; i++){
				int numSent = SDLNet_TCP_Send(connections[i], buffer, bufferPos);
				if(numSent < bufferPos){exit(1);}
			}
			bufferPos = 0;
		}
		//tell anyone waiting that data has been sent
		pthread_cond_signal(&isBufferEmpty);
	}
	pthread_mutex_unlock( &connectionLock );
	pthread_mutex_unlock( &sendAllLock );
	pthread_mutex_unlock( &bufferLock );
}

void NetworkHandler::checkIncoming(int streamToWatch){
	while(live){
		//std::cout << "IS\n";
		readSingleFrame(streamToWatch);
		//std::cout << "IE\n";
		pthread_yield();
	}
}
/*
//look across all network connections and, if any are ready, read a frame from them
void NetworkHandler::getFromNetwork(){
	int numready = SDLNet_CheckSockets(socketState, TIMEOUT);
	if(numready == -1){exit(1);}
	std::cout << "Locking connections." << numready << "\n";
	pthread_mutex_lock( &connectionLock );
	if(numready){
		std::cout << "Reading from all ready.\n";
		int i;
		for(i = 0; i<numConnections; i++){
			if( SDLNet_SocketReady(connections[i]) ){
				readSingleFrame(i);
			}
		}
	}
	pthread_mutex_unlock( &connectionLock );
}
*/
void NetworkHandler::readSingleFrame(int connectionID){
	
	//std::cout << "Reading a single frame.";
	//std::cout << "RT\n";
	char targetIDBuff[4];
	int res = SDLNet_TCP_Recv(connections[connectionID], targetIDBuff, 4);
	if(res<=0){exit(1);}
	int targetID = SDLNet_Read32(targetIDBuff);
	//std::cout << "RL\n";
	char lengthBuff[4];
	res = SDLNet_TCP_Recv(connections[connectionID], lengthBuff, 4);
	if(res<=0){exit(1);}
	int length = SDLNet_Read32(lengthBuff);
	//std::cout << "RM\n";
	char *buffer = (char*)malloc(length);
	res = SDLNet_TCP_Recv(connections[connectionID], buffer, length);
	if(res<=0){exit(1);}
	//std::cout << "UT\n";
	NetworkTarget *target = toUpdate[targetID];
	if(target == NULL){
		std::cout << "Null target " << targetID << "\n";
	}
	else{
		pthread_mutex_lock( &targetLock );
		target->messageUpdate(buffer, length);
		free(buffer);
		pthread_mutex_unlock( &targetLock );
	}
	//std::cout << "Read " << length << " bytes.\n";
}

void NetworkHandler::addTarget(int id, NetworkTarget *target){
	pthread_mutex_lock( &targetLock );
	
	toUpdate[id] = target;
	//std::cout << "Target " << id << " is " << toUpdate[id] << "\n";
	pthread_mutex_unlock( &targetLock );
}

void NetworkHandler::removeTarget(int id){
	pthread_mutex_lock( &targetLock );
	
	toUpdate.erase(id);
	
	pthread_mutex_unlock( &targetLock );
}

void NetworkHandler::addInputHandler(int streamToRead){
	//I assume the lock for the connection is already held
	NetworkInputInfo *handler = (NetworkInputInfo*)malloc(sizeof(NetworkInputInfo));
	handler->handler = this;
	handler->streamToWatch = streamToRead;
	pthread_create( &(inputThread[streamToRead]), NULL, runInput, handler);
}

